"""
Copyright 2009, Jay Shurtz.
 
This is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.
 
This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
 
You should have received a copy of the GNU Lesser General Public
License along with this software; if not, write to the Free
Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.

Special tab completion module.
"""  

HEAD_URL = "$HeadURL: https://taggle.jira.com/svn/TOOLS/trunk/python_modules/custom_tabcomplete.py $"
REVISION = "$Revision: 1812 $"
AUTHOR = "$Author: jayshurtz $"
DATE = "$Date: 2010-07-14 13:40:55 +1000 (Wed, 14 Jul 2010) $"
ID = "$Id: custom_tabcomplete.py 1812 2010-07-14 03:40:55Z jayshurtz $"
version = " ".join(ID.split()[2:4])


import traceback
import rlcompleter
try:
    import readline
except ImportError, e:
    from os import name
    print "Warning: Custom tab completion does not work on the '" + name + "' platform.  Try installing readline or pyreadline."

else:

    readline.parse_and_bind("tab: complete")    # "key: function"

    class CustomCompleter(rlcompleter.Completer):
        """ Special tab completion method.
            Any object defining a __dir__ method will use the list that it returns when 
            evaluating options for tab completion.  This allows an object to override 
            the default behaviour, which is to use the list returned by the built-in 
            function dir().  
            Tab completion varries between different versions of python,
            but this class emulates the newer behaviour which calls an object's __dir__ 
            method, so hopefully all other code designed to work with this class will
            be future-proof (see http://docs.python.org/library/rlcompleter.html ,
            http://effbot.org/librarybook/readline.htm )
        """
        def attr_matches(self, text):   # Override inherited class function called by complete().
            """
            Compute matches when text contains a dot.
             
            Assuming the text is of the form NAME.NAME....[NAME], and is
            evaluatable in 'self.namespace', it will be evaluated and its attributes
            (as revealed by dir()) are used as possible completions.  (For class
            instances, class members are also considered.)
             
            WARNING: this can still invoke arbitrary C code, if an object
            with a __getattr__ hook is evaluated.
            """
            try:
                divIndex = text.rfind(".")
                baseName = text[:divIndex]
                finalName = text[divIndex:].strip(".")
                matches = []
                try:
                    #matches = self.namespace[baseName].__dir__()    # Get matches from __dir__() method if it exists.
                    names = baseName.split(".")
                    if len(names) > 1:
                        base = self.namespace[names[0]] # self.namespace is very useful.
                        for name in names[1:]:
                            try:
                                base = base.__getattribute__(name)
                            except AttributeError, e:
                                base = base.__getattr__(name)
                    else:
                        base = self.namespace.get(baseName, None)
                    matches = base.__dir__()    # Get matches from __dir__() method if it exists.
                    #print "\n__dir__ matches: " + str(matches)      
                except AttributeError, e:
                    matches = rlcompleter.Completer.attr_matches(self, text)    # Get the usual matches instead.
                else:
                    matches = [baseName + "." + match for match in matches if match.startswith(finalName)]
                    #print "\n\nCustomCompleter.matches: " + str(matches)      
            except Exception, e:
                print '\n' + traceback.format_exc()    # For debugging purposes. All exceptions raised by this function are ignored elsewhere!
            return matches


    # Set tab-completion object, print helpful instructions.
    if True:
        custom_completer = CustomCompleter()
        readline.set_completer(custom_completer.complete)  # Use CustomCompleter attr_matches method.
        #print "Custom tab completion enabled."
        from sys import version as pyversion
        #if not pyversion.startswith('2.5.2'):
        #    print 'Warning: Custom tab completion may not work with this version of python.'
    #else:
    #    print "Warning: Normal tab completion enabled. May not work as desired."


if __name__ == "__main__":  # Test.

    def get_item_as_attribute(dictionary={'a':{'aa':{'aab':'AAB'},'ab':'AB'},'b':'B'}, attr='a.aa.aab'):
        """ Get items from nested dictionaries with attribute syntax.
            This works but tab-completion on "." requires a custom class, not a dict.
        """
        names = attr.split(".")
        item = dictionary
        for name in names:
            item = item[name]
        return item

    class D(object):
        def __dir__(self):
            return self.__dict__.keys() + ['whoo!']

    bbb = D()
    bbb.ddx = 'X'
    bbb.ddd = D()
    bbb.ddd.yyy = 'Y'
    bbb.ddd.eee = D()
    bbb.ddd.eee.zzz = 'Z'
    print "!!! Try a tab complete  on bbb."


